<template>
  <a-form
    class="dy-form"
    layout="vertical"
    :hide-required-mark="isCheck"
    :class="isCheck ? 'check-detail-form' : ''"
    :scroll-to-first-error="true"
  >
    <div :class="isFlexRow ? 'md:flex md:flex-row md:flex-wrap md:-mr-11' : ''">
      <template v-for="el in formItemData" :key="el.key">
        <div v-if="el.subTitle" class="w-full mb-4.5 text-lg text-color-header font-semibold">
          {{ el.subTitle }}
        </div>
        <a-form-item
          v-else-if="!el.hide"
          :class="isFlexRow ? 'md:w-1/2 md:!pr-11' : ''"
          :label="el.label"
          v-bind="!disabled && validateInfos && !isCheck ? validateInfos[el.key] : null"
        >
          <span v-if="isCheck" class="ant-form-text">{{ getCheckText(el, model) }}</span>
          <AInput v-else-if="el.loading" :disabled="true" :placeholder="t('common.loading')"></AInput>
          <!--3.0.0-beta.8 a-cascader 不能用component动态渲染-->
          <a-cascader
            v-else-if="el.type === FormItemComponentType.CASCADER"
            :disabled="disabled"
            v-bind="(el.props as any) || {}"
            :value="model[el.key]"
            @change="
              (e) => {
                updateModelOnChange(e, el.key);
              }
            "
          />
          <!--3.0.0-beta.8 a-tree-select用component渲染会有如下警告 warning.js:6 Warning: `children` of TreeSelect is deprecated. Please use `treeData` instead. -->
          <a-tree-select
            v-else-if="el.type === FormItemComponentType.TREESELECT"
            :disabled="disabled"
            v-bind="(el.props as any) || {}"
            :value="model[el.key]"
            @change="
              (e) => {
                updateModelOnChange(e, el.key);
              }
            "
          />
          <component
            :is="getFormItemComponent(el)"
            v-else
            :value="el.type === FormItemComponentType.SWITCH ? undefined : model[el.key]"
            :checked="model[el.key]"
            :disabled="disabled"
            v-bind="el.props || {}"
            @change="
              (e) => {
                updateModelOnChange(e, el.key);
              }
            "
            @before-upload="$emit('beforeUpload')"
            @upload="$emit('upload')"
          >
            {{ el.text || "" }}
          </component>
          <component
            :is="typeof el.extraComponent == 'function' ? el.extraComponent({ curAction }) : el.extraComponent"
            v-if="el.extraComponent"
          />
        </a-form-item>
      </template>
    </div>
  </a-form>
</template>
<script lang="ts" setup>
import { TableColumnProps } from "ant-design-vue";
import { CURDAction, FormItemComponentType, isSelectComponent } from "/@/components/CURDDialog/models";
import type { DynamicFormExpose, FormItemData, ModelRules } from "/@/components/CURDDialog/models";
import type { TableRecord } from "/@/api/models";
import type { FormRules, SelectOptionItem } from "/@/types/antd";
import { useI18n } from "/@/locales/useI18n";
import { computed, onMounted, reactive, ref } from "vue";
import {
  Input as AInput,
  Radio,
  Checkbox as ACheckbox,
  DatePicker as ADatePicker,
  Select as ASelect,
  Cascader as ACascader,
  TreeSelect as ATreeSelect,
  Switch as ASwitch,
  InputNumber as AInputNumber,
  TimePicker as ATimePicker,
  Form,
} from "ant-design-vue";
import ImgUpload from "/@/components/common/ImgUpload.vue";
import { getRules } from "/@/utils/verify";
const ARadioGroup = Radio.Group;

const props = withDefaults(
  defineProps<{
    forceFlexRow?: boolean;
    forceColumn?: boolean;
    columns?: TableColumnProps<TableRecord>[];
    model: TableRecord;
    modelDateFields?: string[];
    modelFormSetting?: FormItemData[];
    modelRules?: ModelRules;
    onBeforeResetFields?: (data: FormItemData[]) => TableRecord;
    idKey?: string;
    curAction?: CURDAction;
    disabled?: boolean;
  }>(),
  {
    curAction: CURDAction.CREATE,
  }
);
const emits = defineEmits(["beforeUpload", "upload", "init"]);
const { t } = useI18n();
// const isCreate = computed(() => props.curAction === CURDAction.CREATE);
const isCheck = computed(() => props.curAction === CURDAction.CHECK);
const isFlexRow = computed(() => {
  return (
    ((formItemData.value.length > 4 && props.curAction === CURDAction.CREATE) || props.forceFlexRow) &&
    !props.forceColumn
  );
});

let data: FormItemData[] = [];
for (let key in props.model) {
  if (key === props.idKey) continue;
  const item: FormItemData = {
    label: "",
    key,
  };
  if (props.columns) {
    for (let i = 0; i < props.columns.length; i++) {
      if (props.columns[i].dataIndex === key) {
        item.label = props.columns[i].title as string;
        break;
      }
    }
  }
  data.push(item);
}
if (props.modelFormSetting) {
  const modelFormSetting = [...props.modelFormSetting];
  for (let j = 0; j < modelFormSetting.length; j++) {
    const item = modelFormSetting[j];
    if (item.subTitle) continue;
    if (item.type === FormItemComponentType.DATEPICKER || item.type === FormItemComponentType.RANGEPICKER) {
      if (!item.props) {
        item.props = {};
      }
      if (item.props.valueFormat === undefined) {
        item.props.valueFormat = t("common.dateTimeFormatStr");
      }
    }
    let isExist = false;
    for (let i = 0; i < data.length; i++) {
      if (item.key === data[i].key) {
        isExist = true;
        Object.assign(data[i], item);
        break;
      }
    }
    if (!isExist) {
      data.push(item);
    }
  }
  //处理subTitle
  modelFormSetting.forEach((el) => {
    if (el.key === "_subTitle" && el.subTitle && el.beforeKey) {
      const index = data.findIndex((item) => item.key === el.beforeKey);
      index !== -1 && data.splice(index, 0, el);
    }
  });
}
const formItemData = ref(data);
function updateModelOnChange(e, key) {
  let value;
  if (e && e.target) {
    value = e.target.value;
    if (value === undefined) {
      value = e.target.checked;
    }
  } else {
    value = e;
  }
  Object.assign(props.model, {
    [key]: value,
  });
}
//表单校验规则
const formRules: FormRules = {};
props.modelRules?.forEach((item) => {
  if (typeof item === "string") {
    const formItem = formItemData.value.find((el) => el.key === item);
    if (formItem) {
      formRules[item] = getRules({
        required: true,
        pattern: formItem.pattern,
        messageName: isSelectComponent(formItem.type as FormItemComponentType) ? "" : formItem.label,
        selectMessageName: formItem.label,
        min: formItem.min,
        max: formItem.max,
      });
    } else {
      formRules[item] = [
        {
          required: true,
          message: t("common.required"),
        },
      ];
    }
  } else {
    Object.assign(formRules, item);
  }
});
formItemData.value.forEach((item) => {
  if ((item.pattern || item.min || item.max || item.required) && !formRules[item.key]) {
    formRules[item.key] = getRules({
      pattern: item.pattern,
      messageName: isSelectComponent(item.type as FormItemComponentType) ? "" : item.label,
      selectMessageName: item.label,
      min: item.min,
      max: item.max,
      required: item.required,
    });
  }
});
const { validate, validateInfos, resetFields: _resetFields } = Form.useForm(props.model, reactive(formRules));
function resetFields() {
  let newValues: TableRecord | undefined;
  if (props.onBeforeResetFields) {
    newValues = props.onBeforeResetFields(formItemData.value);
  }
  _resetFields(newValues);
}
function getFormItemComponent(el: FormItemData) {
  if (el.customComponent) {
    return el.customComponent;
  }
  if (!el.type || el.type === FormItemComponentType.TEXT) {
    return AInput;
  }
  if (el.type === FormItemComponentType.NUMBER) {
    return AInputNumber;
  }
  if (el.type === FormItemComponentType.TEXTAREA) {
    return AInput.TextArea;
  }
  if (el.type === FormItemComponentType.SELECT && el.props && el.props.options) {
    return el.props.options.length <= 3 && !el.props.mode ? ARadioGroup : ASelect;
  }
  if (el.type === FormItemComponentType.DATEPICKER) {
    return ADatePicker;
  }
  if (el.type === FormItemComponentType.CHECKBOX) {
    return ACheckbox;
  }
  if (el.type === FormItemComponentType.CASCADER) {
    return ACascader;
  }
  if (el.type === FormItemComponentType.RANGEPICKER) {
    return ADatePicker.RangePicker;
  }
  if (el.type === FormItemComponentType.IMGUPLOAD) {
    return ImgUpload;
  }
  if (el.type === FormItemComponentType.TREESELECT) {
    return ATreeSelect;
  }
  if (el.type === FormItemComponentType.PASSWORD) {
    return AInput.Password;
  }
  if (el.type === FormItemComponentType.SWITCH) {
    return ASwitch;
  }
  if (el.type === FormItemComponentType.TIMEPICKER) {
    return ATimePicker;
  }
  return AInput;
}
//初始化需要异步加载数据的组件，比如Select
async function initFormItemComponentData() {
  const tasks: Promise<boolean>[] = [];
  for (let i = 0; i < formItemData.value.length; i++) {
    const el = formItemData.value[i];
    if (el.props && el.getData) {
      const p = new Promise<boolean>((resolve) => {
        if (!el.getData) {
          resolve(true);
          return;
        }
        el.loading = true;
        el.getData({ action: props.curAction, item: el })
          .then((data) => {
            if (data && el.props) {
              if (el.type === FormItemComponentType.TREESELECT) {
                el.props.treeData = data;
              } else if (el.customComponent) {
                el.props.data = data;
              } else {
                el.props.options = data;
              }
            }
            resolve(true);
          })
          .finally(() => {
            el.loading = false;
          });
      });
      tasks.push(p);
    }
  }
  return Promise.all(tasks);
}
function getCheckText(el: FormItemData, model: TableRecord) {
  //el.text || model[el.key]
  let val: string | number = "";
  if (el.getText) {
    val = el.getText(model[el.key], model);
  } else {
    val = el.text || model[el.key];
  }
  if (val === null || val === undefined || val === "") {
    return "--";
  }
  return val;
}
const requiredFields: { [key: string]: number } = {};
onMounted(() => {
  emits("init");
});
const dyExpose: DynamicFormExpose = {
  getOptionItemByValue(forItemKey, optionValue) {
    const item = formItemData.value.find((el) => el.key === forItemKey);
    if (item && item.props) {
      const options = item.props.options as SelectOptionItem[];
      if (options && options.length > 0) {
        return options.find((el) => typeof el === "object" && el.value === optionValue);
      }
    }
  },
  reloadFormItemComponentData(formItemKey, params) {
    const item = formItemData.value.find((el) => el.key === formItemKey);
    if (item && item.getData) {
      item.loading = true;
      item
        .getData({ action: props.curAction, item, params })
        .then((data) => {
          if (data && item.props) {
            if (item.type === FormItemComponentType.TREESELECT) {
              item.props.treeData = data;
            } else {
              item.props.options = data;
            }
          }
        })
        .finally(() => {
          item.loading = false;
        });
    }
  },
  validate,
  resetFields,
  getFormItemData() {
    return formItemData.value;
  },
  setFieldRquired(key: string, required = false) {
    const rules = formRules[key];
    if (rules) {
      rules.forEach((rule) => {
        rule.required = required;
      });
    }
    const validateInfo = validateInfos[key];
    if (validateInfo) {
      validateInfo.required = required;
    }
  },
  initFormItemComponentData,
  //隐藏或显示字段
  toggleField(key, hide) {
    const item = formItemData.value.find((el) => el.key === key);
    if (!item) {
      return;
    }
    if (hide) {
      //隐藏要去掉必填规则
      const validateInfo = validateInfos[key];
      if (validateInfo && validateInfo.required) {
        requiredFields[key] = 1;
      }
      dyExpose.setFieldRquired(key, false);
    } else {
      if (requiredFields[key]) {
        //还原必填规则
        dyExpose.setFieldRquired(key, true);
        delete requiredFields[key];
      }
    }
    item.hide = hide;
  },
};
defineExpose(dyExpose);
</script>
<style lang="less">
.dy-form {
  .ant-form-item-explain.ant-form-item-explain-error {
    //触发多个错误的时候 只显示第一个
    height: 24px;
    overflow: hidden;
  }
}
</style>
